#include <iostream>
#include <fstream>
#include "contacts.pb.h"

void AddPeopleInfo(contacts2::PeopleInfo* people)
{
    std::cout << "-----------添加联系⼈成功-----------" << std::endl;

    // 1. 设置联系人姓名，年龄
    std::cout << "请输入联系人的姓名: ";
    std::string name;
    std::getline(std::cin,name);
    people->set_name(name);

    std::cout << "请输入联系人的年龄：";
    int age;
    std::cin >> age;
    people->set_age(age);
    std::cin.ignore(256, '\n');  // 表示清除输入缓冲区的所有的内容，第二个参数表明，遇到 \n 就不清除了，清除 \n（包括\n）之前的内容；第一个参数表示如果清除了256字符还未遇到 \n，就也会停止清除

    // 2.设置联系人电话
    for(int i = 0;;i++)
    {
        std::cout <<  "请输入联系人的电话：" << i+1 << "(只输入回车代表完成电话新增)";
        std::string number;
        std::getline(std::cin, number);
        if(number.empty()) break;
        // Phone是一个message，是存在PeopleInfo的内部message，所以命名一般是中间加一个 _
        // 由于在 contacts.proto 中定义的电话号码是一个 Phone (message)类型的，所以我们得先新增一个 Phone 类型，再往里面的number去设置电话号码
        contacts2::PeopleInfo_Phone* phone = people->add_phone();
        phone->set_number(number);

        std::cout << "请输入该电话的类型:1、MP(移动电话)  2、TLE(固定电话)  ";
        int type;
        std::cin >> type;
        std::cin.ignore(256, '\n');
        switch(type)
        {
            case 1:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
                break;
            case 2:
                phone->set_type(contacts2::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TLE);
                break;
            default:
                std::cout << "输入错误！" << std::endl;
                break;
        }
    }
    // 3.填写地址信息，先设置到Address中
    contacts2::Address address;
    std::string home_address;
    std::cout << "请输入家庭住址：";
    std::getline(std::cin, home_address);
    address.set_home_address(home_address); 
    std::string unit_address;
    std::cout << "请输入单位住址：";
    std::getline(std::cin, unit_address);
    address.set_unit_address(unit_address); 
    // 接下来将Address类型转换成Any类型
    people->mutable_data()->PackFrom(address); // 先用mutable_data 开辟一段空间，接着在这段空间中用Any的 Packfrom 方法将Address类型转换成Any类型


    // 4.设置联系人的其他联系方式
    std::cout << "请选择要添加的其他联系方式：1、qq  2、wechat  ";
    int type;
    std::cin >> type;
    std::cin.ignore(256, '\n');  // 注意要清除一下输入缓冲区的换行符，不然直接自动换行
    if(type == 1)
    {
        std::cout << "请输入联系人的qq号：";
        std::string qq;
        std::getline(std::cin, qq);
        people->set_qq(qq);
    }
    else if(type == 2)
    {
        std::cout << "请输入联系人的wechat号：";
        std::string wechat;
        std::getline(std::cin, wechat);
        people->set_wechat(wechat);
    }
    else std::cout << "选择有误，未成功设置其他联系方式！" << std::endl;

    // 5.设置联系人备注信息
    for(int i = 0;;i++)
    {
        std::cout << "请输入该联系人的第" << i+1 << "个备注标题（直接回车代表输入备注完成）：";
        std::string key;
        std::getline(std::cin, key);
        if(key.empty()) break;
        std::cout << "请输入备注信息：";
        std::string value;
        std::getline(std::cin, value);
        people->mutable_remark()->insert({key,value});
    }

    std::cout << "-----------添加联系⼈成功-----------" << std::endl;
}

int main()
{
    /* GOOGLE_PROTOBUF_VERIFY_VERSION 宏: 验证没有意外链接到与编译的头⽂件不兼容的库版
    本。如果检测到版本不匹配，程序将中⽌。注意，每个 .pb.cc ⽂件在启动时都会⾃动调⽤此宏。在使
    ⽤ C++ Protocol Buffer 库之前执⾏此宏是⼀种很好的做法，但不是绝对必要的。*/
    GOOGLE_PROTOBUF_VERIFY_VERSION;

    contacts2::Contacts contacts;
    // 1.读取本地已经存在的通讯录文件
    std::fstream input("contacts.bin", std::ios::in | std::ios::binary); // 由于序列化之后是二进制的文件，所以以.bin结尾，并且以二进制的方式读取
    if(!input)
    {
        // 如果没有找到这个文件，会自动新建一个
        std::cout << "contacts.bin not find, create new file" << std::endl;
    }
    else if(!contacts.ParseFromIstream(&input))  // 反序列化，从istream中获取
    {
        std::cerr << "反序列化失败！" << std::endl;
        input.close();  // 关闭文件流
        return -1;
    }
    // 2.向通讯录中添加一个新的联系人
    AddPeopleInfo(contacts.add_contacts());  // contacts是一个数组形式，里面生成了add方法，相当于往数组中添加元素，调用了这个方法之后，会帮我们自动开辟好空间供我们使用
    
    // 3.将通讯录写入本地文件中
    std::fstream output("contacts.bin", std::ios::out | std::ios::trunc | std::ios::binary);  // trunc表示覆盖式的写入
    if(!contacts.SerializePartialToOstream(&output))
    {
        std::cout << "序列化失败！" << std::endl;
        input.close();
        output.close();
        return -1;
    }
    std::cout << "write success!" << std::endl;
    // 关闭文件流
    input.close();
    output.close();

    /* 在程序结束时调⽤ ShutdownProtobufLibrary()，为了删除 Protocol Buffer 库分配的所
    有全局对象。对于⼤多数程序来说这是不必要的，因为该过程⽆论如何都要退出，并且操作系统将负责
    回收其所有内存。但是，如果你使⽤了内存泄漏检查程序，该程序需要释放每个最后对象，或者你正在
    编写可以由单个进程多次加载和卸载的库，那么你可能希望强制使⽤ Protocol Buffers 来清理所有
    内容。*/
    google::protobuf::ShutdownProtobufLibrary();

    return 0;
}